/*
 * Copyright (c) 2010, Jim Hollinger
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *   * Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 *   * Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution.
 *   * Neither the name of Jim Hollinger nor the names of its contributors
 *     may be used to endorse or promote products derived from this
 *     software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 */

#define _USE_MATH_DEFINES
#include <math.h>

#include "debughelper/debughelper.h"
#include "dtime/dtime.h"
#include "kmlio/kmlwriter.h"
#include "wpsstatus.h"
#include "wpsinterface.h"


using namespace wpsjgp;


// wps api callback for location updates
static WPS_Continuation wpsjgp::wps_periodic_callback(void *arg,
                                                      WPS_ReturnCode code,
                                                      const WPS_Location *location,
                                                      const void * /* reserved */) {
	WPS_Continuation rc = WPS_STOP;

	WpsInterface *object = (WpsInterface *) arg;
	if (object != NULL) {
		if (code == WPS_OK) {
			double bearing_rad = -location->bearing * M_PI / 180.0;  // convert from ccw degrees to cw radians
			double speed_mps   = location->speed * 3600 / 1000.0;  // convert from km/hr to m/s
			object->newLocation(location->latitude, location->longitude, location->altitude, bearing_rad, speed_mps, location->hpe,
				location->nap, location->ncell, location->nsat, code, WpsInterface::WPS_ErrorToString(code));
			printf("Lat/Lon %.6f / %.6f\t+/-%.0fm\t%d APs\t%d Cells\t%d Sats\n",
				location->latitude, location->longitude, location->hpe,
				location->nap, location->ncell, location->nsat);
		} else {
			object->setErrorCode(code, WpsInterface::WPS_ErrorToString(code));
			fprintf(stderr, "wps_periodic_callback: %s\n", WpsInterface::WPS_ErrorToString(code));
		}
		rc = object->isStop() ? WPS_STOP : WPS_CONTINUE;
	}

    return rc;
}

// wps api callback for tile downloads
static WPS_Continuation wpsjgp::wps_tiling_callback(void *arg,
                                                    unsigned int tileNumber,
                                                    unsigned int tileTotal) {
	WPS_Continuation rc = WPS_STOP;

	WpsInterface *object = (WpsInterface *) arg;
	if (object != NULL) {
		object->setTileDownloads(tileNumber + 1, tileTotal);
		printf("wps_tiling_callback downloaded %d / %d tiles\n", tileNumber + 1, tileTotal);
		rc = object->isStop() ? WPS_STOP : WPS_CONTINUE;
	}

	return rc;
}

// pthread to start wps api location updates
static void *wpsjgp::wps_start_pthread(void *arg) {
	WpsInterface *object = (WpsInterface *) arg;
	if (object != NULL) {
		object->run();
		printf("wps_start_pthread returning ...\n");
	}
	return arg;
}


// static
const unsigned int WpsInterface::tilesTotalDataSize   = (20 * 1024 * 1024);  // 20 MB

// static
const char *WpsInterface::WPS_ErrorToString(int rc) {
	const char *s = NULL;

	switch (rc) {

	case WPS_OK:									s = "WPS_OK";									break;
	case WPS_ERROR_SCANNER_NOT_FOUND:				s = "WPS_ERROR_SCANNER_NOT_FOUND";				break;
	case WPS_ERROR_WIFI_NOT_AVAILABLE:				s = "WPS_ERROR_WIFI_NOT_AVAILABLE";				break;
	case WPS_ERROR_NO_WIFI_IN_RANGE:				s = "WPS_ERROR_NO_WIFI_IN_RANGE";				break;
	case WPS_ERROR_UNAUTHORIZED:					s = "WPS_ERROR_UNAUTHORIZED";					break;
	case WPS_ERROR_SERVER_UNAVAILABLE:				s = "WPS_ERROR_SERVER_UNAVAILABLE";				break;
	case WPS_ERROR_LOCATION_CANNOT_BE_DETERMINED:	s = "WPS_ERROR_LOCATION_CANNOT_BE_DETERMINED";	break;
	case WPS_ERROR_PROXY_UNAUTHORIZED:				s = "WPS_ERROR_PROXY_UNAUTHORIZED";				break;
	case WPS_ERROR_FILE_IO:							s = "WPS_ERROR_FILE_IO";						break;
	case WPS_ERROR_INVALID_FILE_FORMAT:				s = "WPS_ERROR_INVALID_FILE_FORMAT";			break;
	case WPS_ERROR_TIMEOUT:							s = "WPS_ERROR_TIMEOUT";						break;
	case WPS_NOT_APPLICABLE:						s = "WPS_NOT_APPLICABLE";						break;
	case WPS_NOMEM:									s = "WPS_NOMEM";								break;
	case WPS_ERROR:									s = "WPS_ERROR";								break;
	default:										s = "WPS_UNKNOWN_ERROR";						break;

	}

	return s;
}

WpsInterface::WpsInterface() {
	thread_ID.p         = NULL;
	thread_ID.x         = 0;
	pthread_mutex_init(&status_mutex, NULL);
	status              = new WpsStatus();
	my_authentication   = new WPS_SimpleAuthentication;
	WPS_SimpleAuthentication *authentication = (WPS_SimpleAuthentication *) my_authentication;
	if (authentication != NULL) {
		authentication->username = NULL;
		authentication->realm    = NULL;
	}
	kmlWriter           = new kmlio::KmlWriter();

	period_ms           = 1000;

	stop_flag           = false;
	tilesToDownload     = 0;
	tilesDownloaded     = 0;
}

WpsInterface::~WpsInterface() {
	stop();

	// wait for wps api thread to stop
	pthread_join(thread_ID, NULL);
	dtime::delaySec(period_ms / 1000.0);
	printf("~WpsInterface exiting ...\n");

	if (kmlWriter != NULL) {
		kmlWriter->close();
		delete kmlWriter;
		kmlWriter = NULL;
	}
	if (my_authentication != NULL) {
		WPS_SimpleAuthentication *authentication = (WPS_SimpleAuthentication *) my_authentication;

        delete []authentication->realm;
		authentication->realm = NULL;

        delete []authentication->username;
		authentication->username = NULL;

        delete my_authentication;
		my_authentication = NULL;
	}
	delete status;
	status = NULL;

	pthread_mutex_destroy(&status_mutex);
}

bool WpsInterface::init(const std::string &username, const std::string &realm, const std::string &tiles_directory, const std::string &kml_filename) {
	bool flag = false;

	if ((status == NULL) || (my_authentication == NULL) || (kmlWriter == NULL)) {
		fprintf(stderr, "WpsInterface.init: All objects not created\n");
	} else {
		WPS_ReturnCode rc = WPS_ERROR;

		// make copies of the authentication strings
		char *my_username = new char[username.length() + 1];
		strcpy(my_username, username.c_str());
		char *my_realm = new char[realm.length() + 1];
		strcpy(my_realm, realm.c_str());

		// construct authentication object
		WPS_SimpleAuthentication *authentication = (WPS_SimpleAuthentication *) my_authentication;
		authentication->username = my_username;
		authentication->realm    = my_realm;

		// setup local tiling
		rc = WPS_set_tiling(authentication,
							tiles_directory.c_str(),
							tilesTotalDataSize / 10,
							tilesTotalDataSize,
							wps_tiling_callback,
							this);
		if (rc == WPS_OK) {
			flag = true;
			if ((kmlWriter != NULL) && !kml_filename.empty()) {
				kmlWriter->setPretty(false);
				if (kmlWriter->open(kml_filename)) {
					kmlWriter->addXmlDeclaration();
					kmlWriter->addKml();
					kmlWriter->addDocument("WPS", "WPS position fixes");
					kmlWriter->addStyle("transPurpleLineGreenPoly");
					kmlWriter->addLineStyle("7fff00ff", 4);
					kmlWriter->addPolyStyle("7f00ff00");
					kmlWriter->close1();  // style
					kmlWriter->addPlacemark("", "", "#transPurpleLineGreenPoly");
					kmlWriter->addLineString("clampToGround");
					kmlWriter->addCoordinates();
				}
			}
			printf("WpsInterface.init Tiling to \"%s\"\n", tiles_directory.c_str());
		} else {
			fprintf(stderr, "WpsInterface.init: WPS_set_tiling failed: %s\n", WPS_ErrorToString(rc));
		}
	}

	return flag;
}

bool WpsInterface::start(double interfaceRate_hz) {
	bool flag = false;

	// compute update interval for wps api location updates, used in run() method
	if (interfaceRate_hz < 0.001) {
		interfaceRate_hz = 0.001;
	}
	period_ms  = (unsigned long) (1000 / interfaceRate_hz);

	// create thread for wps api location updates
	int rc = pthread_create(&thread_ID, NULL, wps_start_pthread, this);
	if (rc == 0) {
		printf("WpsInterface started, WPS API Version %s\n", WPS_version());
		flag = true;
	} else {
		fprintf(stderr, "WpsInterface.start: pthread_create failed: %d\n", rc);
	}

	return flag;
}

bool WpsInterface::run() {
	bool flag = false;

	WPS_SimpleAuthentication *authentication = (WPS_SimpleAuthentication *) my_authentication;
	unsigned int  iterations = 0;  // forever, until stopped
	WPS_ReturnCode rc = WPS_ERROR;
	rc = WPS_periodic_location(authentication,
							   WPS_NO_STREET_ADDRESS_LOOKUP,
							   period_ms,
							   iterations,
							   wps_periodic_callback,
							   this);
	if (rc == WPS_OK) {
		flag = true;
	} else {
		fprintf(stderr, "WpsInterface.start: WPS_periodic_location failed: %s\n", WPS_ErrorToString(rc));
	}

	return flag;
}

void WpsInterface::stop() {
	if (!stop_flag) {
		stop_flag = true;
		printf("WpsInterface.stop stopping ...\n");
	}
}

bool WpsInterface::isStop() const {
	return stop_flag;
}

void WpsInterface::getStaus(WpsStatus *dst_status) {

	if ((dst_status != NULL) && !isStop()) {
		pthread_mutex_lock(&status_mutex);
		{
			*dst_status = *status;
		}
		pthread_mutex_unlock(&status_mutex);
	}
}

void WpsInterface::newLocation(double latitude_degN, double longitude_degE, double altitude_m, double bearing_rad, double speed_mps, double horizontalError_m,
							   int numAccessPoints, int numCellTowers, int numSatellites, int status_code, const char *status_str) {

	if (!isStop()) {
		pthread_mutex_lock(&status_mutex);
		{
			status->time_s              = dtime::getTime_s();
			status->latitude_degN       = latitude_degN;
			status->longitude_degE      = longitude_degE;
			status->altitude_m          = altitude_m;
			status->bearing_rad         = bearing_rad;
			status->speed_mps           = speed_mps;
			status->horizontalError_m   = horizontalError_m;
			status->numAccessPoints     = numAccessPoints;
			status->numCellTowers       = numCellTowers;
			status->numSatellites       = numSatellites;
			status->status_code         = status_code;
			strncpy(status->status_str, status_str, sizeof (status->status_str));
		}
		pthread_mutex_unlock(&status_mutex);

		if (kmlWriter != NULL) {
			kmlWriter->addCoordinate(latitude_degN, longitude_degE);
		}
	}
}

void WpsInterface::setErrorCode(int error_code, const char *error_str) {
	if (!isStop()) {
		pthread_mutex_lock(&status_mutex);
		{
			status->status_code         = error_code;
			strncpy(status->status_str, error_str, sizeof (status->status_str));
		}
		pthread_mutex_unlock(&status_mutex);
	}
}

void WpsInterface::setTileDownloads(unsigned int new_tilesDownloaded, unsigned int new_tilesToDownload) {
	tilesToDownload = new_tilesDownloaded;
	tilesDownloaded = new_tilesToDownload;
}
